package socketmvc.client.ws;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.websocket.client.WebSocket;
import org.tio.websocket.client.config.WsClientConfig;
import org.tio.websocket.common.Opcode;
import org.tio.websocket.common.WsPacket;
import socketmvc.client.context.SocketChannel;
import socketmvc.client.session.SocketCliSession;
import socketmvc.core.codec.IPacketCodec;
import socketmvc.core.config.SocketMvcConfig;
import socketmvc.core.exception.UnknownException;
import socketmvc.core.filter.IFilter;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.util.*;

public class WsClient {
    private static final Logger log = LoggerFactory.getLogger(WsClient.class);

    protected SocketMvcConfig mvcConfig;
    protected WsIoClient client;
    protected SocketChannel globalSocketChannel;
    protected Map<String, String> httpHeaders;

    public WsClient(SocketMvcConfig mvcConfig) {
        this.mvcConfig = mvcConfig;
    }
    public WsClient(SocketMvcConfig mvcConfig,Map<String, String> httpHeaders) {
        this.mvcConfig = mvcConfig;
        this.httpHeaders = httpHeaders;
    }

    public void connect() throws Exception {

        SocketMvcConfig.IpConfig ipConfig = mvcConfig.getIpConfig();
        if (ipConfig == null){
            throw new RuntimeException("ip配置不能为空");
        }
        if (ipConfig.getHost() == null){
            throw new RuntimeException("ip配置的host不能为空");
        }


        // 初始化过滤器链条
        Collection<IFilter> filters = mvcConfig.getFilters();
        if (filters != null && !filters.isEmpty()){
            mvcConfig.getFilterHandler().addFilter(filters);
        }
        // 初始化数据包编解码器
        Collection<IPacketCodec> packetCodecs = mvcConfig.getPacketCodecs();
        if (packetCodecs != null && !packetCodecs.isEmpty()){
            mvcConfig.getProtocolCodecHandler().addProtocolCodec(packetCodecs);
        }

        // 初始化业务处理器
        try {
            Set<String> userPackages = Optional.ofNullable(mvcConfig.getProcessorScanPackage()).orElse(Set.of());
            Set<String> packages = new LinkedHashSet<>(userPackages.size()+1);
            packages.add("socketmvc.core.processor");
            packages.addAll(userPackages);
            mvcConfig.getProcessorMethodHandler().init(packages);
        } catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) {
            log.error("初始化websocket客户端时发生异常===>",e);
            throw new RuntimeException(e);
        }

        client = getWebSocketClient();
        globalSocketChannel = new SocketChannel(
                client.getWsMsgHandler().getSession(),
                client.getWsMsgHandler().getPacketHandler()
        );
        // 连接到服务器
        client.connect();
    }

    public void reconnect() {
        client.reconnect();
    }

    private WsIoClient getWebSocketClient() throws Exception {
        WsIoClient client = new WsIoClient(mvcConfig,httpHeaders);
        // 创建WebSocket客户端实例
        client.wsMsgHandler.setSession(new SocketCliSession() {
            @Override
            public void send(String msg) {
                client.send(msg);
            }

            @Override
            public void send(byte[] msg) {
                client.send(msg);
            }

            @Override
            public void send(ByteBuffer msg) {
                client.send(msg);
            }

            @Override
            public void reconnect() {
                client.reconnect();
            }

            @Override
            public boolean isConnected() {
               return client.isConnected();
            }

            @Override
            public void close() {
                client.close();
            }

            /**
             * 获取本地socket进程的端口号
             * @return 端口号
             */
            @Override
            public int getPort() {
                return client.getPort();
            }
        });
        return client;
    }

    public static class WsIoClient{

        private final SocketMvcConfig.IpConfig ipConfig;
        private final WsMsgHandler wsMsgHandler;
        private final Map<String,String> httpHeaders;
        private org.tio.websocket.client.WsClient client;

        public WsIoClient(SocketMvcConfig mvcConfig,Map<String, String> httpHeaders) throws Exception {
            this.ipConfig = mvcConfig.getIpConfig();
            this.wsMsgHandler = new WsMsgHandler(mvcConfig);
            this.httpHeaders = httpHeaders;
            this.createTioWsClient(this.ipConfig.getHost(),this.wsMsgHandler);
        }

        private void createTioWsClient(String serverUrl, WsMsgHandler wsMsgHandler) throws Exception {
            client = org.tio.websocket.client.WsClient.create(
                    serverUrl,
                    httpHeaders,
                    new WsClientConfig(
                            e -> wsMsgHandler.onOpen((short) 101,"Switching Protocols"),
                            e -> {
                                WsPacket data = e.data;
                                Opcode wsOpcode = data.getWsOpcode();
                                if (wsOpcode == Opcode.BINARY){
                                    wsMsgHandler.onMessage(ByteBuffer.wrap(data.getBody()));
                                } else if (wsOpcode == Opcode.TEXT) {
                                    wsMsgHandler.onMessage(data.getWsBodyText());
                                }
                            },
                            e -> {
                                try {
                                    wsMsgHandler.onClose(e.code, e.reason, e.wasClean);
                                }catch (Exception ex){
                                    wsMsgHandler.onError(ex);
                                }
                            },
                            e -> wsMsgHandler.onError(new UnknownException(e.msg)),
                            e-> {
                                if (e instanceof Exception){
                                    wsMsgHandler.onError((Exception) e);
                                }else {
                                    wsMsgHandler.onError(new UnknownException(e));
                                }
                            }
                    )
            );
            client.getTioClient().getTioClientConfig().setReadBufferSize(40960);
        }

        public void send(String msg) {
            client.getWs().send(msg);
        }

        public void send(byte[] msg) {
            client.getWs().send(msg);
        }

        public void send(ByteBuffer msg) {
            client.getWs().send(msg);
        }

        public void reconnect() {
            try {
                if (client != null){
                    close("client reconnect");
                }
                createTioWsClient(this.ipConfig.getHost(),this.wsMsgHandler);
                client.connect();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public void connect() throws Exception {
            client.connect();
        }

        public boolean isConnected() {
            if (client == null){
                return false;
            }
            if(client.getWs() == null){
                return false;
            }
            int readyState = client.getWs().getReadyState();
            return readyState == WebSocket.OPEN;
        }

        public void close() {
            close("client actively close");
        }

        public void close(String reason) {
            if (client != null){
                if (client.getWs() != null){
                    client.getWs().close(1000,reason);
                }
                client.close();
                client = null;
            }
        }

        public int getPort(){
            if (client != null){
                return client.getClientChannelContext().getClientNode().getPort();
            }
            return 0;
        }

        public WsMsgHandler getWsMsgHandler() {
            return wsMsgHandler;
        }
    }

    public SocketChannel getGlobalSocketChannel() {
        return globalSocketChannel;
    }
}
